001 /* 002 * Copyright 2004-2006 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.metro.info; 020 021 import java.io.Serializable; 022 import java.beans.IntrospectionException; 023 import java.util.ArrayList; 024 import java.lang.reflect.Method; 025 import java.net.URI; 026 import java.net.URL; 027 028 import net.dpml.lang.AbstractDirective; 029 030 import net.dpml.state.State; 031 import net.dpml.state.StateDecoder; 032 import net.dpml.state.StateBuilderRuntimeException; 033 034 /** 035 * This class contains the meta information about a particular 036 * component type. It describes; 037 * 038 * <ul> 039 * <li>Human presentable meta data such as name, version, description etc 040 * useful when assembling a system.</li> 041 * <li>the context that this component requires</li> 042 * <li>the services that this component type is capable of providing</li> 043 * <li>the services that this component type requires to operate (and the 044 * names via which services are accessed)</li> 045 * <li>information about the component lifestyle</li> 046 * <li>component collection preferences</li> 047 * </ul> 048 * 049 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 050 * @version 1.0.1 051 */ 052 public class Type extends AbstractDirective implements Serializable 053 { 054 static final long serialVersionUID = 1L; 055 056 private static final StateDecoder STATE_DECODER = new StateDecoder(); 057 058 private final InfoDescriptor m_info; 059 private final CategoryDescriptor[] m_categories; 060 private final ContextDescriptor m_context; 061 private final ServiceDescriptor[] m_services; 062 private final State m_graph; 063 064 /** 065 * Creation of a new Type instance using a supplied component descriptor, 066 * logging, context, services, and part references. 067 * 068 * @param info information about the component type 069 * @param loggers a set of logger descriptors the declare the logging channels 070 * required by the type 071 * @param context a component context descriptor that declares the context type 072 * and context entry key and value classnames 073 * @param services a set of service descriptors that detail the service that 074 * this component type is capable of supplying 075 * @param graph the state graph 076 * @exception NullPointerException if the info, loggers, state, or context is null 077 */ 078 public Type( 079 final InfoDescriptor info, final CategoryDescriptor[] loggers, 080 final ContextDescriptor context, final ServiceDescriptor[] services, final State graph ) 081 throws NullPointerException 082 { 083 if( null == info ) 084 { 085 throw new NullPointerException( "info" ); 086 } 087 if( null == loggers ) 088 { 089 throw new NullPointerException( "loggers" ); 090 } 091 if( null == context ) 092 { 093 throw new NullPointerException( "context" ); 094 } 095 if( null == graph ) 096 { 097 throw new NullPointerException( "graph" ); 098 } 099 if( null == services ) 100 { 101 m_services = new ServiceDescriptor[0]; 102 } 103 else 104 { 105 m_services = services; 106 } 107 108 m_info = info; 109 m_categories = loggers; 110 m_context = context; 111 m_graph = graph; 112 } 113 114 /** 115 * Return the state graph for the component type. 116 * @return the state graph 117 */ 118 public State getStateGraph() 119 { 120 return m_graph; 121 } 122 123 /** 124 * Return the info descriptor. 125 * 126 * @return the component info descriptor. 127 */ 128 public InfoDescriptor getInfo() 129 { 130 return m_info; 131 } 132 133 /** 134 * Return the set of Logger that this Component will use. 135 * 136 * @return the set of Logger that this Component will use. 137 */ 138 public CategoryDescriptor[] getCategoryDescriptors() 139 { 140 return m_categories; 141 } 142 143 /** 144 * Return TRUE if the logging categories includes a category with 145 * a matching name. 146 * 147 * @param name the logging category name 148 * @return TRUE if the logging category is declared. 149 */ 150 public boolean isaCategory( String name ) 151 { 152 CategoryDescriptor[] loggers = getCategoryDescriptors(); 153 for( int i = 0; i < loggers.length; i++ ) 154 { 155 CategoryDescriptor logger = loggers[ i ]; 156 if( logger.getName().equals( name ) ) 157 { 158 return true; 159 } 160 } 161 return false; 162 } 163 164 /** 165 * Return the ContextDescriptor for component. 166 * 167 * @return the ContextDescriptor for component. 168 */ 169 public ContextDescriptor getContextDescriptor() 170 { 171 return m_context; 172 } 173 174 /** 175 * Get the set of service descriptors defining the set of services that 176 * the component type exports. 177 * 178 * @return the array of service descriptors 179 */ 180 public ServiceDescriptor[] getServiceDescriptors() 181 { 182 return m_services; 183 } 184 185 /** 186 * Retrieve a service descriptor matching the supplied reference. 187 * 188 * @param reference a service descriptor to match against 189 * @return a matching service descriptor or null if no match found 190 */ 191 public ServiceDescriptor getServiceDescriptor( final ServiceDescriptor reference ) 192 { 193 for ( int i = 0; i < m_services.length; i++ ) 194 { 195 final ServiceDescriptor service = m_services[i]; 196 if ( service.matches( reference ) ) 197 { 198 return service; 199 } 200 } 201 return null; 202 } 203 204 /** 205 * Retrieve a service descriptor matching the supplied classname. 206 * 207 * @param classname the service classname 208 * @return the matching service descriptor or null if it does not exist 209 */ 210 public ServiceDescriptor getServiceDescriptor( final String classname ) 211 { 212 for ( int i = 0; i < m_services.length; i++ ) 213 { 214 final ServiceDescriptor service = m_services[i]; 215 if ( service.getClassname().equals( classname ) ) 216 { 217 return service; 218 } 219 } 220 return null; 221 } 222 223 /** 224 * Return a string representation of the type. 225 * @return the stringified type 226 */ 227 public String toString() 228 { 229 return getInfo().toString(); 230 } 231 232 /** 233 * Test is the supplied object is equal to this object. 234 * @param other the other object 235 * @return true if the object are equivalent 236 */ 237 public boolean equals( Object other ) 238 { 239 if( !super.equals( other ) ) 240 { 241 return false; 242 } 243 if( !( other instanceof Type ) ) 244 { 245 return false; 246 } 247 Type t = (Type) other; 248 if( !m_info.equals( t.m_info ) ) 249 { 250 return false; 251 } 252 if( !m_context.equals( t.m_context ) ) 253 { 254 return false; 255 } 256 if( !m_graph.equals( t.m_graph ) ) 257 { 258 return false; 259 } 260 for( int i=0; i<m_categories.length; i++ ) 261 { 262 if( !m_categories[i].equals( t.m_categories[i] ) ) 263 { 264 return false; 265 } 266 } 267 for( int i=0; i<m_services.length; i++ ) 268 { 269 if( !m_services[i].equals( t.m_services[i] ) ) 270 { 271 return false; 272 } 273 } 274 return true; 275 } 276 277 /** 278 * Return the hashcode for the object. 279 * @return the hashcode value 280 */ 281 public int hashCode() 282 { 283 int hash = super.hashCode(); 284 hash ^= m_info.hashCode(); 285 hash ^= m_context.hashCode(); 286 hash ^= m_graph.hashCode(); 287 for( int i = 0; i < m_services.length; i++ ) 288 { 289 hash ^= m_services[i].hashCode(); 290 hash = hash - 163611323; 291 } 292 for( int i = 0; i < m_categories.length; i++ ) 293 { 294 hash ^= m_categories[i].hashCode(); 295 hash = hash + 471312761; 296 } 297 return hash; 298 } 299 300 /** 301 * Utility operation to construct a default type given a supplied class. 302 * @param subject the component implementation class 303 * @return the type descriptor for the class 304 * @exception IntrospectionException if an introspection error occurs 305 */ 306 public static Type createType( Class subject ) 307 throws IntrospectionException 308 { 309 final InfoDescriptor info = new InfoDescriptor( null, subject.getName() ); 310 final CategoryDescriptor[] loggers = new CategoryDescriptor[0]; 311 final ContextDescriptor context = createContextDescriptor( subject ); 312 final ServiceDescriptor[] services = 313 new ServiceDescriptor[]{ 314 new ServiceDescriptor( subject.getName() ) 315 }; 316 State state = loadStateFromResource( subject ); 317 return new Type( info, loggers, context, services, state ); 318 } 319 320 private static ContextDescriptor createContextDescriptor( Class subject ) 321 throws IntrospectionException 322 { 323 EntryDescriptor[] entries = createEntryDescriptors( subject ); 324 return new ContextDescriptor( entries ); 325 } 326 327 private static EntryDescriptor[] createEntryDescriptors( Class subject ) 328 throws IntrospectionException 329 { 330 String classname = subject.getName(); 331 Class[] classes = subject.getClasses(); 332 Class param = locateClass( "$Context", classes ); 333 if( null == param ) 334 { 335 return new EntryDescriptor[0]; 336 } 337 else 338 { 339 // 340 // For each method in the Context inner-interface we construct a 341 // descriptor that establishes the key, type, and required status. 342 // 343 344 Method[] methods = param.getMethods(); 345 ArrayList list = new ArrayList(); 346 for( int i=0; i<methods.length; i++ ) 347 { 348 Method method = methods[i]; 349 String name = method.getName(); 350 if( name.startsWith( "get" ) ) 351 { 352 EntryDescriptor descriptor = 353 createEntryDescriptor( method ); 354 list.add( descriptor ); 355 } 356 } 357 return (EntryDescriptor[]) list.toArray( new EntryDescriptor[0] ); 358 } 359 } 360 361 /** 362 * Creation of a new parameter descriptor using a supplied method. 363 * The method is the method used by the component implementation to get the parameter 364 * instance. 365 */ 366 private static EntryDescriptor createEntryDescriptor( Method method ) 367 throws IntrospectionException 368 { 369 validateMethodName( method ); 370 validateNoExceptions( method ); 371 372 String key = EntryDescriptor.getEntryKey( method ); 373 374 Class returnType = method.getReturnType(); 375 if( method.getParameterTypes().length == 0 ) 376 { 377 // 378 // required context entry 379 // 380 381 validateNonNullReturnType( method ); 382 String type = returnType.getName(); 383 return new EntryDescriptor( key, type, EntryDescriptor.REQUIRED ); 384 } 385 else if( method.getParameterTypes().length == 1 ) 386 { 387 Class[] params = method.getParameterTypes(); 388 Class param = params[0]; 389 if( returnType.isAssignableFrom( param ) ) 390 { 391 String type = param.getName(); 392 return new EntryDescriptor( key, type, EntryDescriptor.OPTIONAL ); 393 } 394 else 395 { 396 final String error = 397 "Context entry assessor declares an optional default parameter class [" 398 + param.getName() 399 + "] which is not assignable to the return type [" 400 + returnType.getName() 401 + "]"; 402 throw new IntrospectionException( error ); 403 } 404 } 405 else 406 { 407 final String error = 408 "Unable to establish a required or optional context entry method pattern on [" 409 + method.getName() 410 + "]"; 411 throw new IntrospectionException( error ); 412 } 413 } 414 415 private static void validateMethodName( Method method ) 416 throws IntrospectionException 417 { 418 if( !method.getName().startsWith( "get" ) ) 419 { 420 final String error = 421 "Method [" 422 + method.getName() 423 + "] does not start with 'get'."; 424 throw new IntrospectionException( error ); 425 } 426 } 427 428 private static void validateNoExceptions( Method method ) 429 throws IntrospectionException 430 { 431 Class[] exceptionTypes = method.getExceptionTypes(); 432 if( exceptionTypes.length > 0 ) 433 { 434 final String error = 435 "Method [" 436 + method.getName() 437 + "] declares one or more exceptions."; 438 throw new IntrospectionException( error ); 439 } 440 } 441 442 private static void validateNonNullReturnType( Method method ) 443 throws IntrospectionException 444 { 445 Class returnType = method.getReturnType(); 446 if( Void.TYPE.equals( returnType ) ) 447 { 448 final String error = 449 "Method [" 450 + method.getName() 451 + "] does not declare a return type."; 452 throw new IntrospectionException( error ); 453 } 454 } 455 456 private static Class locateClass( String postfix, Class[] classes ) 457 { 458 for( int i=0; i<classes.length; i++ ) 459 { 460 Class inner = classes[i]; 461 String name = inner.getName(); 462 if( name.endsWith( postfix ) ) 463 { 464 return inner; 465 } 466 } 467 return null; 468 } 469 470 private static State loadStateFromResource( Class subject ) 471 { 472 String resource = subject.getName().replace( '.', '/' ) + ".xgraph"; 473 try 474 { 475 URL url = subject.getClassLoader().getResource( resource ); 476 if( null == url ) 477 { 478 return State.NULL_STATE; 479 } 480 else 481 { 482 URI uri = new URI( url.toString() ); 483 return STATE_DECODER.loadState( uri ); 484 } 485 } 486 catch( Throwable e ) 487 { 488 final String error = 489 "Internal error while attempting to load component state graph resource [" 490 + resource 491 + "]."; 492 throw new StateBuilderRuntimeException( error, e ); 493 } 494 } 495 }